package utils

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"math/big"
	"math/rand"
	"net/http"
	gourl "net/url"
	"runtime"
	"strconv"
	"strings"
	"time"

	"cashew.com/cashew_common/configs"
	"cashew.com/cashew_common/configs/locales"
	"cashew.com/cashew_common/constants"
	"github.com/beego/beego/v2/core/logs"
	beecontext "github.com/beego/beego/v2/server/web/context"
)

func RemoveDuplicates(input []string) []string {
	// 用于存储唯一字符串的结果切片
	var result []string
	// 辅助 map 用于判断是否已经存在
	seen := make(map[string]bool)

	// 遍历原始切片
	for _, str := range input {
		// 如果字符串未被记录，添加到结果切片并标记
		if !seen[str] {
			result = append(result, str)
			seen[str] = true
		}
	}

	return result
}

// SliceToString 将 []int64 切片元素用逗号拼接成字符串
func SliceToString(slice []int64) string {
	var builder strings.Builder
	for i, num := range slice {
		str := strconv.FormatInt(num, 10)
		if i > 0 {
			builder.WriteString(",")
		}
		builder.WriteString(str)
	}
	return builder.String()
}

// GenerateDirectoryStructure 根据给定的 int64 输入返回目录结构
func GenerateDirectoryStructure(input int64) string {
	// 将输入的 int64 转换为字符串
	inputStr := strconv.FormatInt(input, 10)

	// 构建目录结构
	// 例子: 输入 123456789 输出 "1/2345/6789/"
	var result string
	for i := 0; i < len(inputStr); i += 4 {
		// 获取当前部分（最多4个字符）
		end := i + 4
		if end > len(inputStr) {
			end = len(inputStr)
		}
		// 追加目录部分并添加斜杠
		result += inputStr[i:end] + "/"
	}
	return result
}

func GenerateResultBody(result constants.ErrNo, data interface{}, language string) constants.ResultParams {
	res := constants.ResultParams{
		Result: result,
		Data:   data,
	}

	// 从 locales 中获取消息
	// 如果语言不存在，则使用英文
	locale, ok := locales.Locale[language][result]
	if !ok {
		locale, ok = locales.Locale[locales.DefaultLang][result]
		if !ok {
			locale = "fail"
		}
	}

	res.Msg = locale

	return res
}

func GetLanguageCode(ctx *beecontext.Context) string {
	lang := ctx.Input.Header("Api-Language")
	if lang == "" {
		lang = locales.DefaultLang
	}
	return lang
}

func InSlice[T comparable](slice []T, val T) bool {
	for _, v := range slice {
		if v == val {
			return true
		}
	}
	return false
}

func Md5(str string) string {
	// 获取哈希值并转换为字节切片
	md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(str)))

	return md5Hash
}

func GetDatetime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// HttpCall 通用的HTTP请求方法
// url: 要请求的URL地址 (必填)
// method: HTTP请求方法，如 "GET"、"POST"、"PUT"、"DELETE" 等 (必填)
// headers: 请求头信息，以map[string]string形式传入 (可选)
// body: 请求体数据，可以是多种格式：[]byte, string, io.Reader, 或可JSON序列化的结构体 (可选)
// 返回值: 响应体数据（字节切片形式）和错误信息
func HttpCall(method, url string, headers map[string]string, body interface{}) ([]byte, error) {
	// 创建HTTP客户端
	client := &http.Client{}

	// 准备请求体
	var reqBody io.Reader
	var contentType string

	if body != nil {
		switch v := body.(type) {
		case []byte:
			reqBody = bytes.NewBuffer(v)
		case string:
			reqBody = strings.NewReader(v)
		case io.Reader:
			reqBody = v
		default:

			if headers["Content-Type"] == "" || strings.Contains(strings.ToLower(headers["Content-Type"]), "application/x-www-form-urlencoded") {
				_, ok := v.(map[string]string)
				if !ok {
					return nil, fmt.Errorf("请求体数据格式错误:application/x-www-form-urlencoded格式下请求体数据必须是map[string]string类型")
				}
				// 尝试构建表单数据
				formData := gourl.Values{}
				for key, value := range v.(map[string]string) {
					formData.Set(key, value)
				}
				reqBody = strings.NewReader(formData.Encode())
				contentType = "application/x-www-form-urlencoded"
			} else if strings.Contains(strings.ToLower(headers["Content-Type"]), "application/json") {
				// 尝试JSON序列化
				bodyBytes, err := json.Marshal(v)
				if err != nil {
					return nil, fmt.Errorf("序列化请求体失败: %v", err)
				}
				reqBody = bytes.NewBuffer(bodyBytes)
				contentType = "application/json"
			} else {
				return nil, fmt.Errorf("不支持的Content-Type: %s", headers["Content-Type"])
			}
		}
	}

	// 创建请求对象
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return nil, fmt.Errorf("创建请求对象失败 http.NewRequest(%s, %s, %v): err:%v", method, url, reqBody, err)
	}

	// 设置默认Content-Type（如果未设置且请求有body）
	if reqBody != nil && contentType != "" && headers["Content-Type"] == "" {
		req.Header.Set("Content-Type", contentType)
	}

	// 设置请求头
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("发送请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 读取响应体数据
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应体失败: %v", err)
	}

	// 检查响应状态码
	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return respBody, fmt.Errorf("请求失败，状态码: %d，响应消息: %s", resp.StatusCode, string(respBody))
	}

	return respBody, nil
}

func ApiCall(uri string, serviceCode string, body interface{}) ([]byte, error) {
	// 设置默认请求头
	headers := make(map[string]string)
	headers["Content-Type"] = "application/json"
	headers["Cashew-Service-Code"] = serviceCode
	headers["Cashew-Service-Key"] = configs.ServiceKeys[serviceCode]

	// 调用 HttpCall 函数
	return HttpCall("POST", configs.DomainApi+uri, headers, body)
}

func GetNowDatetime() string {
	t := time.Now()

	// 获取默认时区
	location, err := time.LoadLocation(configs.DefaultTimezone)
	if err != nil {
		return "" // 处理时区加载失败的情况
	}

	t = t.In(location)

	return t.Format("2006-01-02 15:04:05")
}

func GetNowTimestamp() int64 {
	return time.Now().Unix()
}

func DatetimeToTimestamp(datetime string) (int64, error) {
	// 获取默认时区
	location, err := time.LoadLocation(configs.DefaultTimezone)
	if err != nil {
		return 0, err // 处理时区加载失败的情况
	}

	t, err := time.ParseInLocation("2006-01-02 15:04:05", datetime, location)
	if err != nil {
		return 0, err
	}

	return t.Unix(), nil
}

// 根据时间戳和格式返回格式化后的日期字符串
func TimestampToDatetime(timestamp int64, format string) string {
	// 将时间戳转换为 time.Time 对象
	var t time.Time
	if timestamp == 0 {
		t = time.Now().UTC()
	} else {
		t = time.Unix(timestamp, 0).UTC()
	}

	// 获取默认时区
	location, err := time.LoadLocation(configs.DefaultTimezone)
	if err != nil {
		logs.Warn("时区加载失败:", err)
		return "" // 处理时区加载失败的情况
	}

	// 将时间转换为默认时区时间
	t = t.In(location)

	// 返回格式化后的字符串
	return t.Format(format)
}

// 根据时间戳、目标星期几和格式返回该日期对应的目标星期几 targetWeekday: 1到7
func GetWeekdayFormat(timestamp int64, targetWeekday int, format string) string {
	// 将时间戳转换为 time.Time 对象
	t := time.Unix(timestamp, 0).UTC()

	// 获取默认时区
	location, err := time.LoadLocation(configs.DefaultTimezone)
	if err != nil {
		return "" // 处理时区加载失败的情况
	}

	// 将时间转换为默认时区时间
	t = t.In(location)

	// 获取当前星期几 (Sunday = 0, Monday = 1, ..., Saturday = 6)
	currentWeekday := int(t.Weekday())
	if currentWeekday == 0 {
		currentWeekday = 7 // 将 Sunday 转为 7
	}

	// 计算偏移量，确定距离目标星期几的天数
	dayOffset := targetWeekday - currentWeekday

	// 计算该日期的目标星期几
	targetDate := t.AddDate(0, 0, dayOffset)

	// 返回格式化后的目标星期几日期
	return targetDate.Format(format)
}

func GenerateRandomString(length int) string {
	// 字母和数字的字符集
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	// 创建新的随机数生成器，使用当前 Unix 时间戳作为种子
	randSource := rand.NewSource(time.Now().UnixNano())
	r := rand.New(randSource)

	// 生成随机字符串
	var result []byte
	for i := 0; i < length; i++ {
		randomIndex := r.Intn(len(charset)) // 从字符集中随机选一个字符
		result = append(result, charset[randomIndex])
	}

	return string(result)
}

// divideInt64 计算 a 除 b，并保留指定小数位数（直接舍去）
func DivideInt64(a, b int64, precision int) string {
	if b == 0 {
		return "NaN" // 处理除数为 0 的情况
	}

	// 转换为 big.Float 进行高精度计算
	fa := new(big.Float).SetInt64(a)
	fb := new(big.Float).SetInt64(b)
	result := new(big.Float).Quo(fa, fb) // 计算 a / b

	// 转换为百分比（乘 100）
	hundred := new(big.Float).SetFloat64(100)
	result.Mul(result, hundred)

	// 计算缩放因子 (10^precision)
	scale := new(big.Float).SetFloat64(float64(Pow10(precision)))
	result.Mul(result, scale) // 乘以 10^precision

	// 直接舍去小数部分（取整数）
	intPart, _ := result.Int(nil) // 返回 *big.Int

	// 转换回浮点数，恢复小数位数
	truncatedResult := new(big.Float).SetInt(intPart)
	truncatedResult.Quo(truncatedResult, scale) // 除以 10^precision

	// 格式化输出，保留 precision 位小数，并添加 `%`
	return truncatedResult.Text('f', precision)
}

// pow10 计算 10^n，避免浮点数误差
func Pow10(n int) int64 {
	p := int64(1)
	for i := 0; i < n; i++ {
		p *= 10
	}
	return p
}

// PanicTrace 获取错误和调用的堆栈信息, 堆栈范围为:go启动函数到调用此函数所在的函数, 最多32层
func PanicTrace(err interface{}) string {

	var pc [32]uintptr
	n := runtime.Callers(2, pc[:])

	frames := runtime.CallersFrames(pc[:n])
	var strb strings.Builder

	strb.WriteString(fmt.Sprintf("%v\nTrace:\n", err))

	for {
		frame, more := frames.Next()
		strb.WriteString(fmt.Sprintf("  %s %s:%d\n", frame.Function, frame.File, frame.Line))
		if !more {
			break
		}
	}

	return strb.String()
}

func ToJsonString(v interface{}) string {
	if v == nil {
		return ""
	}
	b, err := json.Marshal(v)
	if err != nil {
		logs.Error("json.Marshal(%v):%v", v, err)
		return ""
	}
	return string(b)
}
